針對 Day 22 XGBoost 在全域損失最小化過程中過度吸附中度風險、導致低度風險預測歸零(Macro F1 跌至 0.248)的現象,導入微軟開發的另一梯度提升巨頭——LightGBM(Light Gradient Boosting Machine)。運用其特有的葉節點導向生長、基於直方圖的特徵分箱(Histogram-based Binning)以及多類別權重平衡參數,探討 LightGBM 能否在極速訓練的同時,修復極端風險等級的召回平衡性。
一、 實作前情境:為什麼在 XGBoost 之後還要嘗試 LightGBM?
在 Day 22 中,XGBoost 拿下了全場最高的 43.00% 總準確率,但混淆矩陣揭示它把低度風險(Low Risk)預測歸零,將大量樣本吸入中度風險。
LightGBM 與 XGBoost 雖然同屬梯度提升家族,但底層架構存在核心差異:
1.生長策略差異(Level-wise vs. Leaf-wise):
2.類別非平衡感知與直方圖加速:
# ==========================================
# Day 23:LightGBM 多分類模型實戰與性能對比
# ==========================================
import warnings
warnings.filterwarnings('ignore')
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, f1_score
# 1. 引用中文字型
font_path = 'NotoSansTC-Regular.otf'
my_font = fm.FontProperties(fname=font_path)
# 2. 建立並訓練 LightGBM 分類器
# objective='multiclass', num_class=3
lgb_clf = lgb.LGBMClassifier(
objective='multiclass',
num_class=3,
n_estimators=100,
learning_rate=0.05,
max_depth=4, # 淺層控制避免小樣本過擬合
num_leaves=15, # 控制葉節點上限
min_child_samples=15, # 每個葉節點最少樣本數
class_weight='balanced', # 關鍵:平衡三類別權重,打破多數類吸附
random_state=42,
verbose=-1
)
lgb_clf.fit(X_train, y_train)
# 3. 測試集盲測預測
y_pred_lgb = lgb_clf.predict(X_test)
y_prob_lgb = lgb_clf.predict_proba(X_test)
acc_lgb = accuracy_score(y_test, y_pred_lgb)
macro_f1_lgb = f1_score(y_test, y_pred_lgb, average='macro')
print(f"=== LightGBM 測試集整體準確率 (Accuracy): {acc_lgb * 100:.2f}% ===")
print(f"=== LightGBM Macro Average F1-score: {macro_f1_lgb:.4f} ===\n")
# 4. 輸出詳細分類評估報告 (Precision, Recall, F1-score)
target_names = ['低度風險 (Low)', '中度風險 (Medium)', '高度風險 (High)']
report_dict_lgb = classification_report(y_test, y_pred_lgb, target_names=target_names, output_dict=True)
report_df_lgb = pd.DataFrame(report_dict_lgb).transpose()
print("=== LightGBM 分類性能評估報告 (Classification Report) ===")
display(report_df_lgb.round(3))
# 5. 繪製 1x2 畫布:混淆矩陣熱力圖 + 特徵重要度 (Split Count)
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
sns.set_theme(style="white")
# --- 圖 1:LightGBM 測試集混淆矩陣 ---
cm_lgb = confusion_matrix(y_test, y_pred_lgb)
sns.heatmap(
cm_lgb,
annot=True,
fmt='d',
cmap='PuBuGn',
xticklabels=target_names,
yticklabels=target_names,
cbar=False,
ax=axes[0]
)
axes[0].set_title(f'LightGBM 測試集混淆矩陣\nMacro F1: {macro_f1_lgb:.3f}', fontproperties=my_font, fontsize=13, fontweight='bold')
axes[0].set_xlabel('模型預測等級 (Predicted Label)', fontproperties=my_font, fontsize=12)
axes[0].set_ylabel('真實風險等級 (True Label)', fontproperties=my_font, fontsize=12)
axes[0].set_xticklabels(target_names, fontproperties=my_font, fontsize=10)
axes[0].set_yticklabels(target_names, fontproperties=my_font, fontsize=10)
# --- 圖 2:LightGBM 特徵重要度 (Feature Importance - Split 次數) ---
importances_lgb = pd.Series(lgb_clf.feature_importances_, index=X_train.columns).sort_values(ascending=True)
importances_lgb.plot(
kind='barh',
ax=axes[1],
color='#16a085',
edgecolor='none',
alpha=0.85
)
axes[1].set_title('LightGBM 特徵重要度排序 (Split 分裂次數)', fontproperties=my_font, fontsize=14, fontweight='bold')
axes[1].set_xlabel('特徵被選為分裂節點的次數 (Split Count)', fontproperties=my_font, fontsize=12)
axes[1].set_ylabel('輸入特徵項目', fontproperties=my_font, fontsize=12)
plt.tight_layout()
plt.show()
# 6. 輸出詳細特徵重要度表格
lgb_feat_df = pd.DataFrame({
'特徵項目': importances_lgb.index,
'分裂次數 (Split)': importances_lgb.values
}).sort_values(by='分裂次數 (Split)', ascending=False).reset_index(drop=True)
print("=== LightGBM 特徵分裂次數完整排序表 ===")
display(lgb_feat_df)

